﻿'Copyright (c) 2008, William Severance, Jr., WESNet Designs
'All rights reserved.

'Redistribution and use in source and binary forms, with or without modification, are permitted
'provided that the following conditions are met:

'Redistributions of source code must retain the above copyright notice, this list of conditions
'and the following disclaimer.

'Redistributions in binary form must reproduce the above copyright notice, this list of conditions
'and the following disclaimer in the documentation and/or other materials provided with the distribution.

'Neither the name of William Severance, Jr. or WESNet Designs may be used to endorse or promote
'products derived from this software without specific prior written permission.

'Disclaimer: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
'            OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
'            AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE
'            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
'            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
'            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
'            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
'            IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'Although I will try to answer questions regarding the installation and use of this software when
'such questions are submitted via e-mail to the below address, no promise of further
'support or enhancement is made nor should be assumed.

'Developer Contact Information:
'     William Severance, Jr.
'     WESNet Designs
'     679 Upper Ridge Road
'     Bridgton, ME 04009
'     Phone: 207-647-9375
'     E-Mail: bill@wesnetdesigns.com
'     Website: www.wesnetdesigns.com


Imports System
Imports System.Web
Imports System.IO
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports WESNet.Utilities

Namespace WESNet.HttpHandlers
    Public Class DynamicImageHandler
        Implements IHttpHandler

        Private Server As HttpServerUtility
        Private Request As HttpRequest
        Private Response As HttpResponse
        Private ContentType As String

        Private Quality As Long = 80L
        Private UseOctreeQuantizer As Boolean = True
        Private ErrorImageSize As Size = New Size(100, 100)

        Public ReadOnly Property HandlerName() As String
            Get
                Return "DynamicImageHandler"
            End Get
        End Property

        Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
            Get
                Return False
            End Get
        End Property


        Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) Implements System.Web.IHttpHandler.ProcessRequest
            Server = context.Server
            Request = context.Request
            Response = context.Response

            'If the request has come from somewhere other than our own domain, then stream the
            'error image and exit
            Dim UrlReferrer As System.Uri = Request.UrlReferrer
            If Not UrlReferrer Is Nothing Then
                If Not UrlReferrer.AbsolutePath.ToLower.Contains(Request.ApplicationPath.ToLower) Then
                    StreamErrorImage("Not Authorized To View Image")
                    Exit Sub
                End If
            End If

            Dim CacheKey As String = Nothing
            Dim FilePath As String = Nothing
            Dim Width As Integer
            Dim Height As Integer
            Dim NewWidth As Integer = -1
            Dim NewHeight As Integer = -1
            Dim MaxWidth As Integer = -1
            Dim MaxHeight As Integer = -1
            Dim ScalingFactor As Single = 1.0!
            Dim MagnificationLimit As Single = 2.0!
            Dim ReductionLimit As Single = 0.1!
            Dim Rescale As Boolean = False

            Dim SourceImage As Bitmap = Nothing
            Dim OutputImage As Bitmap = Nothing

            If Not Request.QueryString("guid") Is Nothing AndAlso Request.QueryString("guid").Length > 0 Then
                CacheKey = Request.QueryString("guid")
            End If

            If Not Request.QueryString("path") Is Nothing AndAlso Request.QueryString("path").Length > 0 Then
                Dim f As String = Request.QueryString("path")
                If CacheKey Is Nothing Then
                    FilePath = Server.MapPath(f.Replace("\", "/"))
                Else
                    FilePath = Server.MapPath(Path.Combine(f, CacheKey & ".resource").Replace("\", "/"))
                End If
            End If

            If FilePath Is Nothing Then
                If Not CacheKey Is Nothing Then
                    SourceImage = CType(context.Cache(CacheKey), Bitmap)
                    If SourceImage Is Nothing Then
                        StreamErrorImage("Cached image has expired")
                        Exit Sub
                    End If
                End If
            Else
                If File.Exists(FilePath) Then
                    Dim fs As FileStream = Nothing
                    Try
                        fs = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
                        SourceImage = ImageUtilities.LoadImage(fs)
                        ContentType = ImageUtilities.GetContentType(Path.GetExtension(FilePath))
                    Catch ex As Exception
                        StreamErrorImage("Image file " & FilePath & " cannot be read")
                        Exit Sub
                    Finally
                        If Not fs Is Nothing Then fs.Close()
                        fs = Nothing
                    End Try
                Else
                    StreamErrorImage("Image file " & FilePath & " cannot be found")
                    Exit Sub
                End If
            End If

            If Not Request.QueryString("maxwidth") Is Nothing AndAlso Request.QueryString("maxwidth").Length > 0 Then
                Try
                    MaxWidth = Integer.Parse(Request.QueryString("maxwidth"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("maxheight") Is Nothing AndAlso Request.QueryString("maxheight").Length > 0 Then
                Try
                    MaxHeight = Integer.Parse(Request.QueryString("maxheight"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            'If there's no source image from the cache or from a file then stream a 1 pixel clear GIF
            'and exit
            If SourceImage Is Nothing Then
                StreamBlankImage(MaxWidth, MaxHeight)
                Exit Sub
            End If

            Width = SourceImage.Width
            Height = SourceImage.Height

            If Not Request.QueryString("scaling") Is Nothing AndAlso Request.QueryString("scaling").Length > 0 Then
                Try
                    ScalingFactor = Single.Parse(Request.QueryString("scaling"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("maxscaling") Is Nothing AndAlso Request.QueryString("maxscaling").Length > 0 Then
                Try
                    MagnificationLimit = Single.Parse(Request.QueryString("maxscaling"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("minscaling") Is Nothing AndAlso Request.QueryString("minscaling").Length > 0 Then
                Try
                    ReductionLimit = Single.Parse(Request.QueryString("minscaling"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("newwidth") Is Nothing AndAlso Request.QueryString("newwidth").Length > 0 Then
                Try
                    NewWidth = Integer.Parse(Request.QueryString("newwidth"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("newheight") Is Nothing AndAlso Request.QueryString("newheight").Length > 0 Then
                Try
                    NewHeight = Integer.Parse(Request.QueryString("height"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("rescale") Is Nothing AndAlso Request.QueryString("rescale").Length > 0 Then
                Try
                    Rescale = Boolean.Parse(Request.QueryString("rescale"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If Not Request.QueryString("quality") Is Nothing AndAlso Request.QueryString("quality").Length > 0 Then
                Try
                    Quality = Long.Parse(Request.QueryString("quality"))
                Catch ex As Exception
                    'Eat the exception if cannot parse
                End Try
            End If

            If (MaxWidth <> -1) Or (MaxHeight <> -1) Then
                OutputImage = GetScaledImage(SourceImage, MaxWidth, MaxHeight, Rescale)
            ElseIf (NewWidth <> -1) Or (NewHeight <> -1) Then
                OutputImage = GetScaledImage(SourceImage, NewWidth, NewHeight, ReductionLimit, MagnificationLimit)
            Else
                OutputImage = GetScaledImage(SourceImage, ScalingFactor, ReductionLimit, MagnificationLimit)
            End If

            If OutputImage Is Nothing Then
                StreamErrorImage("Unable To Create Image")
            Else
                StreamImage(OutputImage)
            End If
        End Sub

        Public Overloads Function GetScaledImage(ByVal img As Bitmap, ByVal MaxWidth As Integer, ByVal MaxHeight As Integer, ByVal Rescale As Boolean) As Bitmap
            Dim SourceWidth As Integer = img.Width
            Dim SourceHeight As Integer = img.Height
            Dim RescaleMagnification As Double
            Dim W, H As Integer

            If (MaxWidth > 0) AndAlso (SourceWidth > MaxWidth) Then
                W = MaxWidth
                H = CInt((SourceHeight * W) / SourceWidth)
            Else
                W = SourceWidth
                H = SourceHeight
            End If
            If (MaxHeight > 0) AndAlso (H > MaxHeight) Then
                W = CInt((W * MaxHeight) / H)
                H = MaxHeight
            End If

            If Rescale AndAlso (W < MaxWidth) AndAlso (H < MaxHeight) Then
                RescaleMagnification = Math.Max(Math.Min(MaxWidth / W, MaxHeight / H), 1.0#)
                W = Convert.ToInt32(W * RescaleMagnification)
                H = Convert.ToInt32(H * RescaleMagnification)
            End If

            If W <> SourceWidth Or H <> SourceHeight Then
                Return New Bitmap(img, W, H)
            Else
                Return New Bitmap(img)
            End If
        End Function

        Public Overloads Function GetScaledImage(ByVal img As Bitmap, ByVal NewWidth As Integer, ByVal NewHeight As Integer, ByVal MinReduction As Single, ByVal MaxMagnification As Single) As Bitmap
            Dim SourceWidth As Integer = img.Width
            Dim SourceHeight As Integer = img.Height
            Dim AspectRatio As Single = Convert.ToSingle(SourceHeight / SourceWidth)
            Dim ScalingFactor As Single = 1.0!

            If NewWidth > 0 Then
                ScalingFactor = Convert.ToSingle(NewWidth / SourceWidth)
            ElseIf NewHeight > 0 Then
                ScalingFactor = Convert.ToSingle(NewHeight / SourceHeight)
            End If

            If (ScalingFactor = 1.0!) Or (ScalingFactor < MinReduction) Or (ScalingFactor > MaxMagnification) Then
                Return New Bitmap(img)
            Else
                Return New Bitmap(img, Convert.ToInt32(SourceWidth * ScalingFactor), Convert.ToInt32(SourceHeight * ScalingFactor))
            End If
        End Function

        Public Overloads Function GetScaledImage(ByVal img As Bitmap, ByVal ScalingFactor As Single, ByVal MinReduction As Single, ByVal MaxMagnification As Single) As Bitmap
            Dim SourceWidth As Integer = img.Width
            Dim SourceHeight As Integer = img.Height
            Dim AspectRatio As Single = Convert.ToSingle(SourceHeight / SourceWidth)

            If (ScalingFactor = 1.0!) Or (ScalingFactor < MinReduction) Or (ScalingFactor > MaxMagnification) Then
                Return New Bitmap(img)
            Else
                Return New Bitmap(img, Convert.ToInt32(SourceWidth * ScalingFactor), Convert.ToInt32(SourceHeight * ScalingFactor))
            End If
        End Function

        Private Sub StreamImage(ByVal img As Bitmap)
            If ContentType Is Nothing Then
                ContentType = ImageUtilities.GetContentType(img)
            End If
            StreamImage(img, ContentType)
        End Sub

        Private Sub StreamImage(ByVal img As Bitmap, ByVal ContentType As String)
            Response.Clear()
            If String.IsNullOrEmpty(ContentType) Then
                Response.ContentType = "image/jpeg"
            Else
                Response.ContentType = ContentType
            End If
            Select Case Response.ContentType
                Case "image/jpeg"
                    ImageUtilities.SaveJPEG(img, Quality, Response.OutputStream)
                Case "image/png"
                    ImageUtilities.SavePNG(img, Response.OutputStream)
                Case "image/gif"
                    ImageUtilities.SaveGIF(img, UseOctreeQuantizer, Response.OutputStream)
                Case Else
                    img.Save(Response.OutputStream, ImageUtilities.GetImageFormatFromContentType(Response.ContentType))
            End Select
            Response.End()
            img.Dispose()
        End Sub

        Private Sub StreamErrorImage(ByVal Message As String)
            Dim g As Graphics
            Dim fnt As Font
            Dim fmt As StringFormat
            Dim img As Bitmap
            Try
                img = New Bitmap(ErrorImageSize.Width, ErrorImageSize.Height, Imaging.PixelFormat.Format24bppRgb)
                Dim rectf As New RectangleF(0, 0, img.Width, img.Height)
                fnt = New System.Drawing.Font("Helvetica", 8.0!, FontStyle.Bold, GraphicsUnit.Point)
                fmt = New StringFormat(StringFormatFlags.FitBlackBox)
                With fmt
                    .Alignment = StringAlignment.Center
                    .LineAlignment = StringAlignment.Center
                End With
                g = Graphics.FromImage(img)
                With g
                    .TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias
                    .Clear(Color.LightGray)
                    .DrawString(Message, fnt, Brushes.Red, rectf, fmt)
                End With
                ImageUtilities.StreamImage(img, "image/jpeg", Quality, False, Response)
            Finally
                If Not g Is Nothing Then g.Dispose()
                g = Nothing
                If Not fnt Is Nothing Then fnt.Dispose()
                If Not fmt Is Nothing Then fmt.Dispose()
                img = Nothing
            End Try
        End Sub

        Private Sub StreamBlankImage(ByVal width As Integer, ByVal height As Integer)
            If width <= 0 Then width = 1
            If height <= 0 Then height = 1
            Dim img As Bitmap = ImageUtilities.CreateBlankImage(width, height, Color.Black)
            ImageUtilities.StreamImage(img, "image/jpeg", Quality, False, Response)
        End Sub
    End Class
End Namespace

